# -*- coding: utf-8 -*-
# @Author  : Adolf  Frank
# @Time    : 2022-01-07 09:37

import os
import sys

curPath = os.path.abspath(os.path.dirname(__file__))
rootPath = os.path.split(curPath)[0]
rootPath = os.path.split(rootPath)[0]
# 先引入目录的上级就好
sys.path.append(rootPath)

from elasticsearch import Elasticsearch
from elasticsearch import helpers
from src.utils_local.date_util import getNowTimeToStr

# ES对外提供的接口
# Adolf Frank
# 20211227

class ElasticSearchClass(object):

    def __init__(self, host, port, user=None, passwrod=None):
        self.host = host
        self.port = port
        self.user = user
        self.password = passwrod
        self.index_name = ""
        self.index_type = ""
        self.es = None # ES connect handle
        self.connect()

    # 历史的版本，仅供参考
    def create_index_use_mapping(self, index_name, index_type):
        self.index_name = index_name
        self.index_type = index_type
        '''
        创建索引名称为index_name，类型为index_type的索引
        :param ex: 
        :return:
        '''
        # 创建映射， 这个后来改为传入的形式
        _index_mappings = {
            "mappings": {
                self.index_type: {
                    "properties": {
                        "id": {
                            "type": "long",
                            "index": True
                        },
                        "chinese_item": {
                            "type": "text",
                            "index": True,
                            "analyzer": "ik_max_word",
                            "search_analyzer": "ik_max_word"
                        },
                        # 英文数据无需指定分词器
                        "english_item": {
                            "type": "text",
                            "index": True,
                            #"analyzer": "whitespace"
                            "analyzer": "english"
                        },
                        "first_level": {
                            "type": "string",
                        },
                        "second_level": {
                            "type": "string",
                        },
                        "third_level": {
                            "type": "string",
                        },
                        "source_type": {
                            "type": "string",
                            "index": "not_analyzed"
                        },
                        "remark_str": {
                            "type": "string",
                            "index": "not_analyzed"
                        },
                        "contributor": {
                            "type": "string",
                        },
                        "picture_res": {
                            "type": "string",
                            "index": "not_analyzed"
                        },
                        "xremark": {
                            "type": "string",
                            "index": "not_analyzed"
                        },
                        "chin_exam": {
                            "type": "text",
                            "index": True,
                            "analyzer": "ik_max_word",
                            "search_analyzer": "ik_max_word"
                        },
                        "chin_exam_res": {
                            "type": "string",
                            "index": "not_analyzed"
                        },
                        "eng_exam": {
                            "type": "text",
                            "index": True,
                        },
                        "eng_exam_res": {
                            "type": "string",
                            "index": "not_analyzed"
                        },
                        "chin_abbr": {
                            "type": "string",
                        },
                        "eng_abbr": {
                            "type": "string",
                        },
                        "chin_synonym": {
                            "type": "text",
                        },
                        "eng_synonym": {
                            "type": "string",
                            "index": "not_analyzed",
                        },
                        "chin_define": {
                            "type": "text",
                            "index": True,
                        },
                        "chin_define_res": {
                            "type": "string",
                            "index": "not_analyzed",
                        },
                        "eng_define": {
                            "type": "text",
                            "index": True,
                        },
                        "eng_define_res": {
                            "type": "string",
                            "index": "not_analyzed",
                        },
                        "item_state": {
                            "type": "string",
                            "index": True,
                            "index": "not_analyzed",
                        }
                    }
                }
            }
        }
        if self.es.indices.exists(index=index_name) is not True:
            res = self.es.indices.create(index=index_name, body=_index_mappings)
            print(" 创建成功 ！" + str(res))
        else:
            print(" 创建失败！")

    # 外部引入的_index_mappings body，使用方便
    def create_index_use_mappings_body(self, index_name, index_type, _index_mappings):
        """
        :param index_name:  索引名称
        :param index_type:  类型为index_type的索引
        :param _index_mappings:  # 创建映射
        :return:
        """
        self.index_name = index_name
        self.index_type = index_type
        if self.es.indices.exists(index=index_name) is not True:
            res = self.es.indices.create(index=index_name, body=_index_mappings)
            print(" 创建成功 ！" + str(res))
        else:
            print(" 创建失败！")

    def create_index_used_type_id_body(self, index_name, document_type, id, body):
        """Creates an index and inserts a document in elasticsearch"""
        try:
            # 6.8.2
            response = self.es.index(index=index_name, doc_type=document_type, id=id,body=body)
            # 6.8.8
            #response = self.es.index(index=index_name, id=id,body=body)
            return response
        except Exception as e:
            print(str(e))
            return False

    def insert_data_used_body(self, index_name, document_type, body_data):
        """
        直接插入数据, 需确定是否为重复数据，待改进
        :param index_name:
        :param document_type:
        :param body_data:  example: body_data = {
                                                   "id :　12345，
                                                   "name" :　"英语",
                                                   "count" :　"10"
                                                }
        :return:
        """
        try:
            response = self.es.index(index=index_name, doc_type=document_type,
                                     body=body_data)
            return response
        except Exception as e:
            print(str(e))
            return False

    def create_index_bulk_used_item_list(self, index_name, index_type, item_list):
        """
        批量记载数据， 没有进行测试，等待需要的时候进行验证
        :param index_name:
        :param index_type:
        :param item_list:
        :return:
        """
        all_actions = []
        i = 1
        for cur_line in item_list:
            action = {
                "_index": index_name,
                "_type": index_type,
                "_id": i,  # _id 也可以默认生成，不赋值
                "_source": {cur_line}
            }
            i += 1
            all_actions.append(action)
            # 批量处理
            #helpers.bulk(self.es, actions)
        success, _ = helpers.bulk(self.es, all_actions, index=index_name, raise_on_error=True)
        print('Performed %d actions' % success)


    def create_index_only(self, index_name, document_type):
        """Creates an index without any document entry."""
        try:
            response = self.es.index(index=index_name, doc_type=document_type)
            # print response
            return response
        except Exception as e:
            print(str(e))
            return False

    def connect(self):
        # 服务链接
        # self.es = Elasticsearch(hosts=[{'host': self.host, 'port': self.port}],
        #                         http_auth=(self.user, self.password))
        self.es = Elasticsearch([{'host': self.host, 'port': self.port}],
                                http_auth=(self.user, self.password))
        print('connect server success at time:' + getNowTimeToStr() + " the connect handle is " + str(self.es))


    def close_connect(self):
        # 关闭连接
        #self.es.close()
        self.es.transport.close()
        print('close ES server success at time: {} !'.format(str(getNowTimeToStr())))

    def count(self, index_name, doc_type):
        """
        :param indexname:
        :return: 统计index总数
        """
        response = self.es.count(index=index_name)#, doc_type=doc_type)
        if response is not None:
            return response['count']
        else:
            return 0

    def delete_doc_used_id(self, index_name, doc_type, id):
        """
        :param indexname:
        :param doc_type:
        :param id:
        :return: 根据id删除index中具体的一条
        """
        self.es.delete(index=index_name, doc_type=doc_type, id=id)

    def delete_index(self, index_name):
        """
        直接删除index
        :param index_name:
        :return:
        """
        self.es.indices.delete(index = index_name)

    def search_used_id(self, index_name, index_type, id):
        """
        :param indexname: 根据ID获取具体数据
        :param id:
        :return:
        """
        response = self.es.get(index=index_name, doc_type=index_type, id=id)
        if response is not None and response['found'] == True:
            return response['_source']
        else:
            return None

    def search(self, index_name, size=10):
        """
         默认搜索10条数据, 查看整体数据使用
        :param index_name:
        :param size:
        :return:
        """
        try:
            #return self.es.search(index=indexname, size=size, sort="@timestamp:desc")
            return self.es.search(index=index_name, size=size)
        except Exception as err:
            print(err)

    def search_scroll(self, index_name, size=10):
        """大批量分页搜索 """
        try:
            if size < 10000:
                return self.es.search(index=index_name, size=size)
            else:
                # 获取所有数据
                query_json = {
                    "query": {
                        "match_all": {}
                    }
                }
                page_num = 1000  # 每次获取数据
                query = self.es.search(index=index_name, body=query_json, scroll='5m', size=page_num,timeout='1m')

                results = query['hits']['hits']  # es查询出的结果第一页
                total = query['hits']['total']  # es查询出的结果总量
                scroll_id = query['_scroll_id']  # 游标用于输出es查询出的所有结果
                every_num = int(total / page_num)

                alist = []
                for i in range(0, every_num + 1):
                    # scroll参数必须指定否则会报错
                    query_scroll = self.es.scroll(scroll_id=scroll_id, scroll='5m')['hits']['hits']
                    results += query_scroll
                for key in results:
                    es_data_dict = key["_source"]
                    #print(es_data_dict)
                    alist.append(es_data_dict)

                return alist
        except Exception as err:
            print(err)

    def search_used_field_and_item(self, index_name,doc_type, search_field, search_item, size=5000):
        """
        指定field搜索单个item,直接获取指定字段的结果，不带该条目的其他信息
        :param index_name: 
        :param doc_type: 
        :param search_field:   指定的字段
        :param search_item:    搜索的内容
        :param size:         默认搜索10条数据
        :return: 
        """
        try:
            body = {
                "query": {
                    "term": {
                        search_field: search_item
                    }
                }
            }
            array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=size)
            jsons = array_search["hits"]["hits"]
            # 可能限制输出个数
            print("the hits numbers " + str(len(jsons)) + " in " + index_name + ":" + doc_type)
            # 针对分词错误产生的影响条目，使用以下的解决办法
            # 若果返回为0，表示没有命中，可使用如下方式获取2元组分词错误的结果
            # ngram_list = get_ngram_list(search_item,2)
            #ret_field_list = es_handle.search_used_field_and_items("kis_data_index", "kis_data_index_type",
            #                                                       "chinese_item", ngram_list, 1000)
            #return ret_field_list

            ret_document = []
            # 检索字段并保存字段值
            for hits in jsons:
                ret_document.append(hits['_source'][search_field])
            return ret_document
        except Exception as err:
            print(err)
            return None

    def search_used_field_and_item_body(self,index_name,doc_type, search_field, search_item, size=5000):
        """
        指定field搜索单个item， 以json返回所有数据信息，方便下游使用，常用
        :param index_name:
        :param doc_type:
        :param search_field:   指定的字段
        :param search_item:    搜索的内容
        :param size:         默认搜索10条数据
        :return:
        """
        try:
            body = {
                "query": {
                    "term": {
                        search_field: search_item
                    }
                }
            }
            array_search = self.es.search(index=index_name, doc_type=doc_type, body=body,size=size)
            jsons = array_search["hits"]["hits"]
            #print("the hits numbers " + str(len(jsons)))
            ret_document = []
            # 检索字段并保存字段值
            for hits in jsons:
                ret_document.append(hits['_source'])
            return ret_document
        except Exception as err:
            print(err)
            return None

    def search_used_field_and_items(self, index_name, doc_type, search_field, search_item_list, size=5000):
        """
        指定field搜索多个item
        :param index_name:
        :param doc_type:
        :param search_field:   指定的字段
        :param search_item_list:    搜索的内容，list列表
        :param size:         默认搜索10条数据
        :return:
        """
        try:
            body = {
                "query": {
                    "terms": {
                        search_field: search_item_list
                    }
                }
            }

            array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=size)
            jsons = array_search["hits"]["hits"]
            # 可能限制输出个数
            #print("the hits numbers " + str(len(jsons)))
            ret_document = []
            for hits in jsons:
                ret_document.append(hits['_source'])
            return ret_document
        except Exception as err:
            print(err)
            return None

    # 关于term和match的区别，term是精确匹配，match会模糊化，会进行分词，返回匹配度分数，
    # （term查询字符串接受小写字母，如果有大写会返回空即没有命中，match则是不区分大小写都可以进行查询，返回结果也一样）
    def search_used_field_match_item(self, index_name, doc_type, search_field, search_item, size=5000):
        """
        指定field包含单个item， 模糊匹配
        :param index_name:
        :param doc_type:
        :param search_field:   指定的字段
        :param search_item:    搜索的内容
        :param size:         默认搜索10条数据
        :return:
        """
        try:
            body = {
                "query": {
                    "match": {
                        search_field: search_item
                    }
                }#,
                # "highlight": {
                #     "fields": {
                #         search_field: {}
                #     }
                # }
            }
            # 20220303 suny570 modify
            #array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=size)
            array_search = self.es.search(index=index_name, body=body, size=size)
            jsons = array_search["hits"]["hits"]
            #print("the hits numbers " + str(len(jsons)))
            ret_document = []
            # ES自带的高亮功能
            #ret_hight_arrs = []
            for hits in jsons:
                #ret_document.append(hits['_source'][search_field])
                ret_document.append(hits['_source'])
                # 关键词两边加个<em></em>
                #ret_hight_arrs.append(hits['highlight'])
            #return ret_document, ret_hight_arrs
            return ret_document
        except Exception as err:
            print(err)
            return None

    def search_used_field_match_item_scores(self, index_name, doc_type, search_field, search_item, size=5000):
        """
        指定field包含单个item， 模糊匹配
        :param index_name:
        :param doc_type:
        :param search_field:   指定的字段
        :param search_item:    搜索的内容
        :param size:         默认搜索10条数据
        :return:         含有搜索分数
        """
        try:
            body = {
                "query": {
                    "match": {
                        search_field: search_item
                    }
                }
            }

            #array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=size)
            array_search = self.es.search(index=index_name,  body=body, size=size)
            jsons = array_search["hits"]["hits"]
            #print("the hits numbers " + str(len(jsons)))
            ret_document = []
            ret_scores_arr = []
            for hits in jsons:
                #ret_document.append(hits['_source'][search_field])
                ret_document.append(hits['_source'])
                ret_scores_arr.append(hits['_score'])
            return ret_document, ret_scores_arr
        except Exception as err:
            print(err)
            return None

    def search_used_fields_match_item(self, index_name, doc_type, search_field_list, search_item, size=5000):
        """
        # multi_match:
        :param index_name:
        :param doc_type:
        :param search_field_list:   指定的字段集合
        :param search_item:         搜索的内容
        :param size:         默认搜索10条数据
        :return:
        """
        try:
            
            body = {
                "query": {
                    "multi_match": {
                        "query": search_item,
                        "fields": search_field_list
                    }
                }
            }
            array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=size)
            jsons = array_search["hits"]["hits"]
            # 可能限制输出个数
            #print("the hits numbers " + str(len(jsons)))
            ret_document = []
            for hits in jsons:
                ret_document.append(hits['_source'])
            return ret_document
        except Exception as err:
            print(err)
            return None

    def search_used_prefix_item(self, index_name, doc_type, search_field, prefix_item, size=5000):
        """
        # 查询field中前缀为"prefix_item"的所有数据
        :param index_name:
        :param doc_type:
        :param search_field:   指定的字段
        :param prefix_item:    搜索的内容
        :param size:         默认搜索10条数据
        :return:
        """
        try:

            body = {
                "query": {
                    "prefix": {
                        search_field: prefix_item
                    }
                }
            }

            array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=size)
            jsons = array_search["hits"]["hits"]
            # 可能限制输出个数
            #print("the hits numbers " + str(len(jsons)))
            ret_document = []
            # 检索字段并保存字段值
            for hits in jsons:
                #ret_document.append(hits['_source'][search_field])
                ret_document.append(hits['_source'])
            return ret_document
        except Exception as err:
            print(err)
            return None

    def search_used_suffix_item(self, index_name, doc_type, search_field, suffix_item, size=5000):
        """
        # 查询field中以"*id"为后缀的所有数据
        :param index_name:
        :param doc_type:
        :param search_field:   指定的字段
        :param prefix_item:    搜索的内容
        :param size:         默认搜索10条数据
        :return:
        """
        try:
            # 通配符查询
            body = {
                "query": {
                    "wildcard": {
                        search_field: "*"+suffix_item
                    }
                }
            }
            array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=size)
            jsons = array_search["hits"]["hits"]
            # 哪里可能限制输出个数
            #print("the hits numbers " + str(len(jsons)))
            ret_document = []
            # 检索字段并保存字段值
            for hits in jsons:
                #ret_document.append(hits['_source'][search_field])
                ret_document.append(hits['_source'])
            return ret_document
        except Exception as err:
            print(err)
            return None

    # 目前只设置了两个字段数据的联合查询
    def search_used_bool_must(self,index_name, doc_type, field_list, item_list, size=5000):
        """
        # 匹配多个字段的情况，目前只实现了，两个对应字段，可根据具体需求扩充
        :param index_name:
        :param doc_type:
        :param field_list:   指定的字段
        :param item_list:    搜索的内容与字段对应
        :param size:         默认搜索10条数据
        :return:
        """
        try:
            # 两者为term或者match
            """
            body = {
                "query": {
                    "bool": {
                        "must": [
                            {
                                "match": {
                                    field_list[0]: item_list[0]

                                }
                            },
                            {
                                "match": {
                                    field_list[1]: item_list[1]
                                }
                            }
                        ]
                    }
                }
            }
            """
            match_arr = []
            for i in range(len(field_list)):
                search_condition = {
                                        "match": {
                                            field_list[i]: item_list[i]

                                        }
                                    }
                match_arr.append(search_condition)
            body = {
                "query": {
                    "bool": {
                        "must": match_arr
                    }
                }
            }
            array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=size)
            jsons = array_search["hits"]["hits"]
            # 哪里可能限制输出个数
            #print("the hits numbers " + str(len(jsons)))

            ret_document = []
            for hits in jsons:
                ret_document.append(hits['_source'])
            return ret_document

        except Exception as err:
            print(err)
            return None

    def update_used_id_field_item(self, index_name, doc_type, doc_id, field, item):
        """
        单条指定字段更新，根据ID和field/item修改指定字段内容，待测试
        :param index_name:
        :param doc_type:
        :param doc_id:
        :param field:
        :param item:
        :return:      成功1，失败0
        """
        try:
            # 通配符查询
            body = {
                "doc": {
                    field:item
                }
            }
            response = self.es.update(index=index_name, doc_type=doc_type, id=doc_id, body=body)
            return response['_shards']['successful']

        except Exception as err:
            print(err)
            return None

    def update_used_id_fielditem_list(self, index_name, doc_type, doc_id, field_list, item_list):
        """
        单条数据更行多个字段， field_list和item_list数目必须一致，待测试
        :param index_name:
        :param doc_type:
        :param doc_id:
        :param field_list:
        :param item_list:
        :return:      成功1，失败0
        """
        if len(field_list) != len(item_list):
            return False

        data_dict = {}
        for field, item in zip(field_list, item_list):
            data_dict[field] = item
        try:
            # 通配符查询
            body = {
                "doc": {
                    data_dict
                }
            }
            response = self.es.update(index=index_name, doc_type=doc_type, id=doc_id, body=body)
            return response['_shards']['successful']

        except Exception as err:
            print(err)
            return None

    def search_top_records(self,index_name, doc_type, field_, begin_num, top_size):
        """
        # 对某一个字段进行排序，取top_size的数目
        :param index_name:
        :param doc_type:
        :param field_:   指定的字段(必须为intger类型)
        :param begin_num:    搜索的内容与字段对应
        :param top_size:         默认搜索10条数据
        :return:
        """
        try:
            # 两者为term或者match
            body = {
                "query": {
                    "range": {
                        field_: {
                            "gte": 0,  # >=18
                            #    "lte": 30  # <=30
                        }
                    }
                },
                "from": begin_num,
                "size": top_size,
                # 重复的丢掉，显示内容较少
                # "collapse": {
                #     "field": str(field_),
                # },
                "sort": {
                    field_: {  # 根据field_字段升序排序
                        "order": "desc"  # asc升序，desc降序
                    }
                }
            }
            # body = {
            #     "query": {
            #         "match_all": {}
            #     },
            #     "sort": {
            #         field_: {  # 根据field_字段升序排序
            #             "order": "desc"  # asc升序，desc降序
            #         }
            #     }
            # }
            # 为了提高速度总是获取5000条
            #array_search = self.es.search(index=index_name, doc_type=doc_type, body=body, size=5000)
            array_search = self.es.search(index=index_name, body=body, size=top_size)
            jsons = array_search["hits"]["hits"]
            # print("the hits numbers " + str(len(jsons)))
            ret_document = []
            for hits in jsons:
                ret_document.append(hits['_source'])
            # print("ret_document:",ret_document)
            return ret_document

        except Exception as err:
            print(err)
            return None


